﻿#region license
// Copyright (c) 2004, Rodrigo B. de Oliveira (rbo@acm.org)
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// 
//     * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//     * Neither the name of Rodrigo B. de Oliveira nor the names of its
//     contributors may be used to endorse or promote products derived from this
//     software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# Taken and modified from the original.

# This file is part of Grawlix.
# 
# Grawlix is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# 
# Grawlix is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public
# License along with Grawlix.  If not, see
# <http://www.gnu.org/licenses/>.
#endregion

"""Base class providing compilation support."""

namespace Grawlix.Meta.Tests

import System.Reflection
import System
import System.Diagnostics
import System.IO
import Boo.Lang.Compiler
import Boo.Lang.Compiler.IO
import Boo.Lang.Compiler.Steps
import Boo.Lang.Compiler.Pipelines
import NUnit.Framework


public abstract class AbstractCompilerTestCase:
"""Base class for test fixtures that require compilation."""

	[TestFixtureSetUp]
	public virtual def SetUpFixture():
	"""Generic setup information."""
		System.Threading.Thread.CurrentThread.CurrentCulture = \
			System.Globalization.CultureInfo.InvariantCulture
		path = Uri(Assembly.GetExecutingAssembly().CodeBase)
		path = Uri(path, ".")
		_baseTestCasesPath = Path.Combine(
			path.LocalPath,
			RelativeTestCasesPath)
		_compiler = BooCompiler()
		_parameters = _compiler.Parameters
		_parameters.OutputWriter = (_output = StringWriter())
		_parameters.Pipeline = SetUpCompilerPipeline()
		_parameters.References.Add(typeof(NUnit.Framework.Assert).Assembly)
#		_parameters.References.Add(typeof(AbstractCompilerTestCase).Assembly)
#		_parameters.References.Add(typeof(BooCompiler).Assembly)
		_parameters.References.Add(
			typeof(Grawlix.Meta.ComparisonAttribute).Assembly)
		_parameters.OutputAssembly = \
			Path.Combine(Path.GetTempPath(), 'testcase.exe')
		#_parameters.Defines.Add(
		#	'BOO_COMPILER_TESTS_DEFINED_CONDITIONAL', null)
		CustomizeCompilerParameters(_parameters)
		if VerifyGeneratedAssemblies:
			CopyDependencies()

	
	protected virtual def CustomizeCompilerParameters(
		parameters as CompilerParameters):
	"""
	Modify the compiler parameters.
	Param parameters: A reference to the compiler parameters.
	"""
		pass
	
	protected virtual def CopyDependencies():
	"""Copy all required dependencies over to the execution folder."""
		pass
#		CopyAssembly(typeof(Boo.Lang.List).Assembly)
#		CopyAssembly(typeof(Boo.Lang.Extensions.MacroMacro).Assembly)
#		CopyAssembly(GetType().Assembly)
#		CopyAssembly(typeof(NUnit.Framework.Assert).Assembly)
#if !VISUAL_STUDIO
#		CopyAssembly(System.Reflection.Assembly.Load('BooModules'))
#endif
	
	protected def CopyAssembliesFromTestCasePath():
	"""Use a custom set of assemblies from the test case path."""
		for fname as string in Directory.GetFiles(_baseTestCasesPath, '*.dll'):
			CopyAssembly(fname)

	public def CopyAssembly(assembly as System.Reflection.Assembly):
	"""
	Copy a particular assembly over to the execution folder.
	Param assembly: The assembly to copy.
	"""
		if assembly is null:
			raise ArgumentNullException('assembly')
		CopyAssembly(assembly.Location)

	
	public def CopyAssembly(location as string):
	"""
	Copy a particular assembly over to the execution folder.
	Param location: The file path of the assembly.
	"""
		File.Copy(
			location,
			Path.Combine(
				Path.GetTempPath(),
				Path.GetFileName(location)),
			true)
	
	[TestFixtureTearDown]
	public virtual def TearDownFixture():
	"""Clean up this fixture."""
		Trace.Listeners.Clear()
	
	[SetUp]
	public virtual def SetUpTest():
	"""Do per-test setup."""
		current as System.Threading.Thread = \
			System.Threading.Thread.CurrentThread
		current.CurrentCulture = \
			current.CurrentUICulture = \
				System.Globalization.CultureInfo.InvariantCulture
		_parameters.Input.Clear()
	
	protected virtual def SetUpCompilerPipeline():
	"""
	Create the compiler pipeline.
	Returns: The compiler pipeline to run.
	"""
		pipeline as CompilerPipeline
		if VerifyGeneratedAssemblies:
			pipeline = CompileToFileAndVerify()
		else:
			pipeline = CompileToMemory()
		pipeline.Add(RunAssembly())
		return pipeline
	
	private def GetEnvironmentFlag(name as string, defaultValue as bool):
	"""
	Grab boolean environmental variables.
	Param name: The name of the variable.
	Param defaultValue: If the variable is not present, the value to return.
	Returns: The value of the environmental variable, or defaultValue.
	"""
		value as string = Environment.GetEnvironmentVariable(name)
		if value is null:
			return defaultValue
		return bool.Parse(value)

	
	protected def RunCompilerTestCase(*files as (string)):
	"""
	Compile and run a test case.
	Param files: The names of the files to run.
	"""
		for file as string in files:
			_parameters.Input.Add(FileInput(GetTestCasePath(file)))
		RunAndAssert()
	
	protected def RunAndAssert():
	"""Run the test case and check output."""
		context as CompilerContext
		output as string = Run(null, context)
		expected as string = context.CompileUnit.Modules[0].Documentation
		if expected is null:
			expected = ''
		Assert.AreEqual(
			expected.Trim(),
			output.Trim(),
			_parameters.Input[0].Name)
	
	protected def RunString(code as string):
	"""
	Run a code string.
	Param code: The code to test.
	Returns: The output of the program.
	"""
		return RunString(code, null)
	
	protected def RunString(code as string, stdin as string):
	"""
	Run a code string, specifying console input.
	Param code: The code to test.
	Param stdin: The console input to use, or null.
	Returns: The output of the program.
	"""
		_parameters.Input.Add(StringInput('<teststring>', code))
		context as CompilerContext
		return Run(stdin, context)
	
	private def HasErrors(context as CompilerContext):
	"""
	Check if an operation had errors compiling.
	Param context: The compiler context to check.
	Returns: true if errors were found, else false.
	"""
		return (context.Errors.Count > 0)

	
	protected def Run(stdin as string, ref context as CompilerContext):
	"""
	Run the test case.
	Param stdin: The console input to use, or null.
	Param context: out: The context used to execute the code.
	Returns: The output text.
	"""
		oldStdOut as TextWriter = Console.Out
		oldStdIn as TextReader = Console.In
		
		try:
			Console.SetOut(_output)
			if stdin is not null:
				Console.SetIn(StringReader(stdin))
			
			context = _compiler.Run()
			
			if HasErrors(context) and (not IgnoreErrors):
				Assert.Fail( \
					context.Parameters.Input[0].Name + ': ' + \
					context.Errors.ToString(false) + \
					context.Warnings.ToString())
			return _output.ToString().Replace('\r\n', '\n')
		ensure:
			_output.GetStringBuilder().Length = 0
			
			Console.SetOut(oldStdOut)
			Console.SetIn(oldStdIn)
	
	private def GetTestCasePath(fname as string):
	"""
	Get the path to the test case.
	Param fname: The file name.
	Returns: The full path to the file.
	"""
		return Path.Combine(_baseTestCasesPath, fname)
	
	private class AssemblyResolver:

		private _path as string

		
		public def constructor(path as string):
			_path = path

		
		public def AssemblyResolve(sender as object, args as ResolveEventArgs) as System.Reflection.Assembly:
			simpleName as string = GetSimpleName(args.Name)
			basePath as string = Path.Combine(_path, simpleName)
			asm as Assembly = ProbeFile((basePath + '.dll'))
			if asm is not null:
				return asm
			return ProbeFile((basePath + '.exe'))

		
		private def GetSimpleName(name as string) as string:
			return System.Text.RegularExpressions.Regex.Split(name, ',\\s*')[0]

		
		private def ProbeFile(fname as string) as Assembly:
			if not File.Exists(fname):
				return null
			try:
				return Assembly.LoadFrom(fname)
			except x as Exception:
				Console.Error.WriteLine(x)
			return null

	
	protected def InstallAssemblyResolver(path as string) as System.ResolveEventHandler:
		handler as ResolveEventHandler = AssemblyResolver(path).AssemblyResolve
		AppDomain.CurrentDomain.AssemblyResolve += handler
		return handler

	
	protected def RemoveAssemblyResolver(handler as System.ResolveEventHandler):
		AppDomain.CurrentDomain.AssemblyResolve -= handler
	
	protected virtual IgnoreErrors as bool:
	"""Whether to ignore errors on this compilation. Defaults to false."""
		get:
			return false
	
	abstract RelativeTestCasesPath as string:
	"""The path to where the test cases are stored."""
		get:
			pass
	
	private _compiler as BooCompiler
	"""The compiler used to compile test cases."""

	private _parameters as CompilerParameters
	"""Access to the compiler's parameters."""
	
	[getter(BaseTestCasesPath)]
	private _baseTestCasesPath as string
	"""Base path for testcases."""

	private _output as StringWriter
	"""The output data to use."""

	private VerifyGeneratedAssemblies:
	"""Whether to verify the assemblies."""
		get:
#if VISUAL_STUDIO
#			return false
#else
			return GetEnvironmentFlag("peverify", true)
#endif

